/*
 * $Id: DateUtils.java 3100 2008-10-14 22:33:10Z rah003 $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx.calendar;

import java.util.Calendar;
import java.util.Date;

/**
 * Utility methods for Date manipulation.
 * <p>
 * 
 * This utility class is replaced by CalendarUtils because day related
 * manipulation are meaningfull relative to a Calendar only. Always doing so
 * against the default calendar instance isn't enough.
 * 
 * PENDING JW: move the missing ops. Volunteers, please! Once done, this will be
 * deprecated.
 * 
 * @author Scott Violet
 * @version $Revision: 3100 $
 * 
 */
public class DateUtils {
	private static Calendar CALENDAR = Calendar.getInstance();

	/**
	 * Returns the last millisecond of the specified date.
	 *
	 * @param date
	 *            Date to calculate end of day from
	 * @return Last millisecond of <code>date</code>
	 */
	public static Date endOfDay(Date date) {
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTime(date);
			calendar.set(Calendar.HOUR_OF_DAY, 23);
			calendar.set(Calendar.MILLISECOND, 999);
			calendar.set(Calendar.SECOND, 59);
			calendar.set(Calendar.MINUTE, 59);
			return calendar.getTime();
		}
	}

	/**
	 * Returns a new Date with the hours, milliseconds, seconds and minutes set
	 * to 0.
	 *
	 * @param date
	 *            Date used in calculating start of day
	 * @return Start of <code>date</code>
	 */
	public static Date startOfDay(Date date) {
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTime(date);
			calendar.set(Calendar.HOUR_OF_DAY, 0);
			calendar.set(Calendar.MILLISECOND, 0);
			calendar.set(Calendar.SECOND, 0);
			calendar.set(Calendar.MINUTE, 0);
			return calendar.getTime();
		}
	}

	/**
	 * Returns day in millis with the hours, milliseconds, seconds and minutes
	 * set to 0.
	 *
	 * @param date
	 *            long used in calculating start of day
	 * @return Start of <code>date</code>
	 */
	public static long startOfDayInMillis(long date) {
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTimeInMillis(date);
			calendar.set(Calendar.HOUR_OF_DAY, 0);
			calendar.set(Calendar.MILLISECOND, 0);
			calendar.set(Calendar.SECOND, 0);
			calendar.set(Calendar.MINUTE, 0);
			return calendar.getTimeInMillis();
		}
	}

	/**
	 * Returns the last millisecond of the specified date.
	 *
	 * @param date
	 *            long to calculate end of day from
	 * @return Last millisecond of <code>date</code>
	 */
	public static long endOfDayInMillis(long date) {
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTimeInMillis(date);
			calendar.set(Calendar.HOUR_OF_DAY, 23);
			calendar.set(Calendar.MILLISECOND, 999);
			calendar.set(Calendar.SECOND, 59);
			calendar.set(Calendar.MINUTE, 59);
			return calendar.getTimeInMillis();
		}
	}

	/**
	 * Returns the day after <code>date</code>.
	 *
	 * @param date
	 *            Date used in calculating next day
	 * @return Day after <code>date</code>.
	 */
	public static Date nextDay(Date date) {
		return new Date(addDays(date.getTime(), 1));
	}

	/**
	 * Adds <code>amount</code> days to <code>time</code> and returns the
	 * resulting time.
	 *
	 * @param time
	 *            Base time
	 * @param amount
	 *            Amount of increment.
	 * 
	 * @return the <var>time</var> + <var>amount</var> days
	 */
	public static long addDays(long time, int amount) {
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTimeInMillis(time);
			calendar.add(Calendar.DAY_OF_MONTH, amount);
			return calendar.getTimeInMillis();
		}
	}

	/**
	 * Returns the day after <code>date</code>.
	 *
	 * @param date
	 *            Date used in calculating next day
	 * @return Day after <code>date</code>.
	 */
	public static long nextDay(long date) {
		return addDays(date, 1);
	}

	/**
	 * Returns the week after <code>date</code>.
	 *
	 * @param date
	 *            Date used in calculating next week
	 * @return week after <code>date</code>.
	 */
	public static long nextWeek(long date) {
		return addDays(date, 7);
	}

	/**
	 * Returns the number of days difference between <code>t1</code> and
	 * <code>t2</code>.
	 *
	 * @param t1
	 *            Time 1
	 * @param t2
	 *            Time 2
	 * @param checkOverflow
	 *            indicates whether to check for overflow
	 * @return Number of days between <code>start</code> and <code>end</code>
	 */
	public static int getDaysDiff(long t1, long t2, boolean checkOverflow) {
		if (t1 > t2) {
			long tmp = t1;
			t1 = t2;
			t2 = tmp;
		}
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTimeInMillis(t1);
			int delta = 0;
			while (calendar.getTimeInMillis() < t2) {
				calendar.add(Calendar.DAY_OF_MONTH, 1);
				delta++;
			}
			if (checkOverflow && (calendar.getTimeInMillis() > t2)) {
				delta--;
			}
			return delta;
		}
	}

	/**
	 * Returns the number of days difference between <code>t1</code> and
	 * <code>t2</code>.
	 *
	 * @param t1
	 *            Time 1
	 * @param t2
	 *            Time 2
	 * @return Number of days between <code>start</code> and <code>end</code>
	 */
	public static int getDaysDiff(long t1, long t2) {
		return getDaysDiff(t1, t2, true);
	}

	/**
	 * Check, whether the date passed in is the first day of the year.
	 *
	 * @param date
	 *            date to check in millis
	 * @return <code>true</code> if <var>date</var> corresponds to the first day
	 *         of a year
	 * @see Date#getTime()
	 */
	public static boolean isFirstOfYear(long date) {
		boolean ret = false;
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTimeInMillis(date);
			int currentYear = calendar.get(Calendar.YEAR);
			// Check yesterday
			calendar.add(Calendar.DATE, -1);
			int yesterdayYear = calendar.get(Calendar.YEAR);
			ret = (currentYear != yesterdayYear);
		}
		return ret;
	}

	/**
	 * Check, whether the date passed in is the first day of the month.
	 *
	 * @param date
	 *            date to check in millis
	 * @return <code>true</code> if <var>date</var> corresponds to the first day
	 *         of a month
	 * @see Date#getTime()
	 */
	public static boolean isFirstOfMonth(long date) {
		boolean ret = false;
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTimeInMillis(date);
			int currentMonth = calendar.get(Calendar.MONTH);
			// Check yesterday
			calendar.add(Calendar.DATE, -1);
			int yesterdayMonth = calendar.get(Calendar.MONTH);
			ret = (currentMonth != yesterdayMonth);
		}
		return ret;
	}

	/**
	 * Returns the day before <code>date</code>.
	 *
	 * @param date
	 *            Date used in calculating previous day
	 * @return Day before <code>date</code>.
	 */
	public static long previousDay(long date) {
		return addDays(date, -1);
	}

	/**
	 * Returns the week before <code>date</code>.
	 *
	 * @param date
	 *            Date used in calculating previous week
	 * @return week before <code>date</code>.
	 */
	public static long previousWeek(long date) {
		return addDays(date, -7);
	}

	/**
	 * Returns the first day before <code>date</code> that has the day of week
	 * matching <code>startOfWeek</code>. For example, if you want to find the
	 * previous monday relative to <code>date</code> you would call
	 * <code>getPreviousDay(date, Calendar.MONDAY)</code>.
	 *
	 * @param date
	 *            Base date
	 * @param startOfWeek
	 *            Calendar constant correspoding to start of week.
	 * @return start of week, return value will have 0 hours, 0 minutes, 0
	 *         seconds and 0 ms.
	 * 
	 */
	public static long getPreviousDay(long date, int startOfWeek) {
		return getDay(date, startOfWeek, -1);
	}

	/**
	 * Returns the first day after <code>date</code> that has the day of week
	 * matching <code>startOfWeek</code>. For example, if you want to find the
	 * next monday relative to <code>date</code> you would call
	 * <code>getPreviousDay(date, Calendar.MONDAY)</code>.
	 *
	 * @param date
	 *            Base date
	 * @param startOfWeek
	 *            Calendar constant correspoding to start of week.
	 * @return start of week, return value will have 0 hours, 0 minutes, 0
	 *         seconds and 0 ms.
	 * 
	 */
	public static long getNextDay(long date, int startOfWeek) {
		return getDay(date, startOfWeek, 1);
	}

	private static long getDay(long date, int startOfWeek, int increment) {
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTimeInMillis(date);
			int day = calendar.get(Calendar.DAY_OF_WEEK);
			// Normalize the view starting date to a week starting day
			while (day != startOfWeek) {
				calendar.add(Calendar.DATE, increment);
				day = calendar.get(Calendar.DAY_OF_WEEK);
			}
			return startOfDayInMillis(calendar.getTimeInMillis());
		}
	}

	/**
	 * Returns the previous month.
	 * 
	 * @param date
	 *            Base date
	 * @return previous month
	 */
	public static long getPreviousMonth(long date) {
		return incrementMonth(date, -1);
	}

	/**
	 * Returns the next month.
	 * 
	 * @param date
	 *            Base date
	 * @return next month
	 */
	public static long getNextMonth(long date) {
		return incrementMonth(date, 1);
	}

	private static long incrementMonth(long date, int increment) {
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTimeInMillis(date);
			calendar.add(Calendar.MONTH, increment);
			return calendar.getTimeInMillis();
		}
	}

	/**
	 * Returns the date corresponding to the start of the month.
	 *
	 * @param date
	 *            Base date
	 * @return Start of month.
	 */
	public static long getStartOfMonth(long date) {
		return getMonth(date, -1);
	}

	/**
	 * Returns the date corresponding to the end of the month.
	 *
	 * @param date
	 *            Base date
	 * @return End of month.
	 */
	public static long getEndOfMonth(long date) {
		return getMonth(date, 1);
	}

	private static long getMonth(long date, int increment) {
		long result;
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTimeInMillis(date);
			if (increment == -1) {
				calendar.set(Calendar.DAY_OF_MONTH, 1);
				result = startOfDayInMillis(calendar.getTimeInMillis());
			} else {
				calendar.add(Calendar.MONTH, 1);
				calendar.set(Calendar.DAY_OF_MONTH, 1);
				calendar.set(Calendar.HOUR_OF_DAY, 0);
				calendar.set(Calendar.MILLISECOND, 0);
				calendar.set(Calendar.SECOND, 0);
				calendar.set(Calendar.MINUTE, 0);
				calendar.add(Calendar.MILLISECOND, -1);
				result = calendar.getTimeInMillis();
			}
		}
		return result;
	}

	/**
	 * Returns the day of the week.
	 *
	 * @param date
	 *            date
	 * @return day of week.
	 */
	public static int getDayOfWeek(long date) {
		Calendar calendar = CALENDAR;
		synchronized (calendar) {
			calendar.setTimeInMillis(date);
			return (calendar.get(Calendar.DAY_OF_WEEK));
		}
	}
}
